/********************************************************
Project name: OOP - Home Assignment no. 3

Authors: Rami Berkovsky
         Ofir Cohen

File name: Parser.cpp

Purpose: Parser for the assembly-like instructions.

Input:  line, delimeter, pointer to a file, command
Output: none

Role: This class perform the following actions:
      1) It reads line-by-line from the given file
      2) Creates a pointer to the corresponding Command
      3) Returns enum answer as specified in enum RETURN_VALUE


********************************************************/

#include <iostream>
#include <fstream>
#include <string>
#include <queue>
#include <vector>
#include <map>
#include "Parser.h"
#include "Command.h"
#include "Exceptions.h"
#include "limits.h"

using namespace std;

RETURN_VALUE Parser::Parse_line(string line, ifstream &fin, string delimeter,Command** cmd) {

    vector<string> words;
                           	          /* this vector holds:     words[0] - command name
                                                                words[1] - <operand1>
                                                                words[2] - <operand2>
                                       */

	trimSpaces(line);	              // remove redundant whitespaces

	if (line.empty())                 // empty line
		return EMPTYLINE;

	split_string(line,words);         // split line into words vector

    // unexpected endloop validation
	if (words[0] == DEL_ENDLOOP)
	{
		if (delimeter != DEL_ENDLOOP)
            throw UnexpectedEndLoop();

		return ENDLOOP;
	}

    // unexpected else validation
	if (words[0] == DEL_ELSE)
	{
		if (delimeter != DEL_IF)
			throw UnexpectedElse();
		return ELSE;
	}

    // unexpected endif validation
	if (words[0] == DEL_ENDIF)
	{
		if (delimeter != DEL_IF && delimeter != DEL_ENDIF)
            throw UnexpectedEndIf();

		return ENDIF;
	}

	if (mapCmd.find(words[0]) == mapCmd.end())
	{
		// command wasn't found
        throw NoSuchCommand();

    } else {

        // too many parameters ?
        if (words.size() < 2)
            throw InvalidParameters();

		
		// valid syntax: <op1> = reg#
		if (words[1].substr(0,3)!= REGNAME)
            throw InvalidRegName();

		// get reg number
		string srcReg = words[1].substr(3,(words[1].size() - 3));

		// valid syntax: op1 = reg#
		if (srcReg.size() != 1 || !((srcReg[0] - '0') > -1 && (srcReg[0] - '0') < 8))
            throw InvalidRegName();


		if (words[0] == LOOPCMD) {

            // too many / insufficient parameters ?
            if (words.size() != 2)
                throw InvalidParameters();

			// make object command
			*cmd = mapCmd[words[0]]->clone();

            (*cmd)->set(atoi(srcReg.c_str()));

			Command *temp = NULL;
			if (fin.eof())
                throw NoEndLoopClause();

            getline(fin,line);
			
			RETURN_VALUE retval = Parse_line(line,fin,DEL_ENDLOOP,&temp);
			while (retval != ENDLOOP) {
				if (retval != EMPTYLINE)
					((CompositeCommand*)(*cmd))->add(temp);

				if (fin.eof())
                    throw NoEndLoopClause();

				getline(fin,line);
				retval = Parse_line(line,fin,DEL_ENDLOOP,&temp);
			}
			return SUCCESS;

		} else if (words[0] == IFCMD) {

            // too many / insufficient parameters ?
            if (words.size() != 3)
                throw InvalidParameters();


		    // valid syntax: <op2> = reg#
		    if (words[2].substr(0,3)!=REGNAME)
                throw InvalidRegName();

			// get reg number
			string dstReg = words[2].substr(3,(words[2].size() - 3));

		    if (dstReg.size() != 1 || !((dstReg[0] - '0') > -1 && (dstReg[0] - '0') < 8))
                throw InvalidRegName();

			*cmd = mapCmd[words[0]]->clone();
			(*cmd)->set(atoi(srcReg.c_str()), atoi(dstReg.c_str()));

			Command *temp = NULL;
			if (fin.eof())
                throw NoEndIfClause();

			getline(fin,line);
			RETURN_VALUE retval = Parse_line(line,fin,DEL_IF,&temp);
			while (retval != ELSE && retval != ENDIF) {
				if (retval != EMPTYLINE)
					((IfCmd*)(*cmd))->add(temp);

				if (fin.eof())
                    throw NoEndIfClause();

                getline(fin,line);
				retval = Parse_line(line,fin,DEL_IF,&temp);
			}

			if (retval == ELSE) {

				getline(fin,line);
				if (fin.eof())
                    throw NoEndIfClause();

				retval = Parse_line(line,fin,DEL_ENDIF,&temp);
				while (retval != ENDIF) {
					if (retval != EMPTYLINE)
						((IfCmd*)(*cmd))->addElse(temp);

					if (fin.eof())
                        throw NoEndIfClause();

                    getline(fin,line);
					retval = Parse_line(line,fin,DEL_ENDIF,&temp);
				}

			}
			return SUCCESS;

		} else {

			// non-block commands

            // too many / insufficient parameters ?
            if (words.size() != 3)
                throw InvalidParameters();


			// make object command
			*cmd = mapCmd[words[0]]->clone();


			if (mapCmd[words[0]]->getType() == TWOREGCMD)
            {
		        // valid syntax: <op2> = reg#
		        if (words[2].substr(0,3) != REGNAME)
                    throw InvalidRegName();

			    // get reg number
			    string dstReg = words[2].substr(3,(words[2].size() - 3));

		        if (dstReg.size() != 1 || !((dstReg[0] - '0') > -1 && (dstReg[0] - '0') < 8))
                    throw InvalidRegName();

                (*cmd)->set(atoi(srcReg.c_str()),atoi(dstReg.c_str()));
            }

			else {
                string srcVal = words[2];
                if (!isNum(srcVal.c_str()))
                        throw InvalidValue();
                errno=0;
                if ((strtoul(srcVal.c_str(),NULL,0) == ULONG_MAX) && (errno != 0))
                        throw InvalidValue();
                
                (*cmd)->set(atoi(srcReg.c_str()),strtoul(srcVal.c_str(),NULL,0));
            }
			return SUCCESS;
		}
	}
	return FAILURE;
}

void Parser::trimSpaces(string &line)
{
	// replace all occourences of tabs with spaces
	replace(line.begin(),line.end(),'\t',' ');


	// cut spaces in the middle
	if (line.size() > 0) {

		for (size_t i=0;i+1<line.size();)
		{
			if (line[i] == ' ' && line[i+1] == ' ')
				line.erase(i+1,1);
			else
				i++;
		}
	}

	// in the beginning
	if (line[0] == ' ')
		line.erase(0,1);
		
	// in the end
	if (line.size() > 0) {
		if (line[line.size() - 1] == ' ')
			line.erase(line.size() - 1);
	}

	size_t found;

	// psikim & stuff
	if (line.size() > 0) {
		found=line.find(',');
		if ((found != string::npos) && (found+1 < line.size())) {
			if (line[found+1]==' ') line.erase(found+1,1);
			if (line[found-1]==' ') line.erase(found-1,1);
		}
	}

}

// split_string - split a line to a vector of words
void Parser::split_string(const string &text,vector<string>& words)
{
  int i=0,counter=0;
  char ch;
  string word;

  while(ch=text[i++])
  {
    if (isspace(ch) || (counter==1 && ch == ','))
    {
      if (!word.empty())
      {
	counter++;
        words.push_back(word);
      }
      word = "";
    }
    else
    {
      word += ch;
    }
  }
  if (!word.empty())
  {
    words.push_back(word);
  }
}

// Map that holds each command name and related object
void Parser::GenerateCmdMap() {
	
		Command *cmd = NULL;
		// build map for commands

		try {
			cmd = new LoadCmd();
			if (cmd == NULL)
				throw MemoryAllocFailed();
			mapCmd.insert(make_pair("load",cmd));

			cmd = NULL;
			cmd = new LoopCmd();
			if (cmd == NULL)
				throw MemoryAllocFailed();
			mapCmd.insert(make_pair("loop",cmd));

			cmd = NULL;
			cmd = new AndCmd();
			if (cmd == NULL)
				throw MemoryAllocFailed();
			mapCmd.insert(make_pair("and",cmd));

			cmd = NULL;
			cmd = new OrCmd();
			if (cmd == NULL)
				throw MemoryAllocFailed();
			mapCmd.insert(make_pair("or",cmd));

			cmd = NULL;
			cmd = new XorCmd();
			if (cmd == NULL)
				throw MemoryAllocFailed();
			mapCmd.insert(make_pair("xor",cmd));

			cmd = NULL;
			cmd = new AddCmd();
			if (cmd == NULL)
				throw MemoryAllocFailed();
			mapCmd.insert(make_pair("add",cmd));

			cmd = NULL;
			cmd = new SubCmd();
			if (cmd == NULL)
				throw MemoryAllocFailed();
			mapCmd.insert(make_pair("sub",cmd));

			cmd = NULL;
			cmd = new AddiCmd();
			if (cmd == NULL)
				throw MemoryAllocFailed();
			mapCmd.insert(make_pair("addi",cmd));

			cmd = NULL;
			cmd = new SubiCmd();
			if (cmd == NULL)
				throw MemoryAllocFailed();
			mapCmd.insert(make_pair("subi",cmd));

			cmd = NULL;
			cmd = new MoveCmd();
			if (cmd == NULL)
				throw MemoryAllocFailed();
			mapCmd.insert(make_pair("move",cmd));

			cmd = NULL;
			cmd = new IfCmd();
			if (cmd == NULL)
				throw MemoryAllocFailed();
			mapCmd.insert(make_pair("if",cmd));

		}
		catch (MemoryAllocFailed &e)
		{
			cout << e.what() << endl;
			exit(1);
		}

}

bool Parser::isNum(const char *s)
{
	while (*s != '\0')
	{
		if (!isdigit(*s))
			return false;
		s++;
	}
	return true;
}

// Destructor - delete all records from map
Parser::~Parser()
{
    map<const string,const Command*>::iterator it;
    
    // Release allocated memory
    for (it = mapCmd.begin(); it != mapCmd.end(); it++)
        delete it->second;

    // Remove all entries from map
    mapCmd.erase(mapCmd.begin(),mapCmd.end());
}
